iT邦幫忙

2022 iThome 鐵人賽

DAY 4
1

前言

在這篇文章中,將會介紹 JavaScript 的 Runtime Environment,讓讀者更了解 JS 的執行過程。


JavaScript Runtime Environment

說到 JS 的 Runtime Environment,那就肯定是要搬出這張圖去做解釋!

左邊的區塊就是 JS 引擎,在引擎內部會有 Call Stack,但是單靠引擎內部運作的話,程式一次只會做一件事,一個任務執行完後才會執行其他任務,也就是同步(Synchronous)。

但瀏覽器,以 Chrome 為例,是**多進程(multi processes) & 多執行緒(multi threads)**的,因此可以同時執行好幾個任務,詳細 Chrome 內部有哪些可參考底下兩個連結:

Inside look at modern web browser

Threading and Tasks in Chrome

其中,那些瀏覽器執行緒裡面處理的事件/任務,像是 setTimeout,XMLHttpRequest等就屬於非同步事件,你可能需要等待一小段時間才能得到 response 的任務。

假如非同步事件會和同步事件都一起放進 Call stack,那麼就會造成阻塞,比如我們發出一個 HTTP 的請求,那麼在完成回傳 response 之前,就會因為 JS 單執行緒的特性,我們在網頁上都無法進行其它動作。

而在 JavaScript Runtime Environment,透過 Event Loop、Task Queue 能讓 JS 順利的執行非同步事件。在 JavaScript Runtime Environment 內部,主要分成了五個部分:

  1. JS Engine
  2. Web API
  3. Task Queue/Callback Queue
  4. Job Queue/Microtask Queue
  5. Event loop

之前已經有介紹過 JS 引擎了,所以就跳過,以下說明另外四個部分:

1. Web API

瀏覽器提供的 API,包括:

  1. DOM API: document.getElementById, addEventListerner
  2. AJAX
  3. Timer 函式: setTimeout、setInterval

2. Task Queue(也可以稱 Callback Queue)

它是一個佇列的資料結構,當 Web API 的非同步事件觸發時,其 callback function 會進入到 Task Queue 裡面,等 Call stack 裡面沒有其它任務才會進入到 Call stack 執行。

主要存放 Macrotasks,像是 setTimeout、setImmediate、UI 互動事件。

Macrotasks 歸類於瀏覽器提供

3. Job Queue(也可以稱 Microtask Queue)

存放 Microtasks,例如 Promise .then/catch/finally 函式、MutationObserver、process.nextTick。

Microtasks 的函式偏向是 V8 引擎提供的 API

Microtasks 會先執行,完成後才執行 Task Queue 內的 Macrotasks

4. Event loop

透過 Event loop,可以判斷是否要將非同步任務加到 Call Stack 執行,其監測步驟如下:

  1. 看看 Call Stack 裡面有沒有任務,有的話執行,沒有就往第 2 步驟
  2. 看看 Job Queue 裡面有沒有 Microtask,有的話就把任務移到 Call stack 執行(跳到第 4 步驟),沒有的話跳第 3 步驟
  3. 看看 Task Queue 裡面有沒有 Macrotask,有的話搬一個 Macrotask 到 Call stack 執行
  4. 執行完任務,DOM 結構有更新則重新渲染畫面,再回到第 1 步驟

執行範例

在以下的網站中,將整個 Runtime Environment 視覺化並附上了一段程式碼,有興趣的讀者可以進去網站並執行程式碼。

latentflip.com/loupe


題目練習

以下兩道題目讀者可以練習看看,如果有不清楚或是回答錯誤的話,可以將前面的內容再多看一下喔~

練習題 1

以下有五個 console.log,試著判斷它們印出的先後順序:

setTimeout(() => {
  console.log('I will be printed out first.');
}, 0);

const promiseExample = new Promise(function (resolve) {
  resolve('But I still faster than you, Mr.SetTimeout. I am a microtask.')
}).then((data) => {
  console.log(data);
})

function mockFunc() {
  console.log('Shut up! I am a synchronous task so I will run first.');
}

mockFunc();

也許從對話就能看出結果了? XD

練習題 2

這段程式碼會印出什麼?

console.log("A");

setTimeout(function () {
  console.log("B");
}, 0);

while(true) {} // 死循環

答案是只會印出 A,由於要等到 Call stack 的同步任務都完成,非同步的任務才會透過 event loop 進到 Call stack 執行,但 while loop 一直不會執行完,所以就不會印出 B。


參考資料 & 推薦閱讀

Day5 [JavaScript 基礎] Event Loop 機制

Demystifying Asynchronous JavaScript — Event Loop, Call stack, Task Queue, and More!


2024/12/11 補充:為什麼要設計成 Microtasks 會先執行,完成後才執行 Macrotasks?

以下 ChatGPT 回答:

Microtasks(微任務)會優先於 Task Queue(宏任務)的原因是與語言設計和性能優化相關,以下是詳細的解釋:

1. 微任務的定義和用途

微任務(Microtasks)是一類執行優先級較高的任務,通常用於處理當前執行上下文中的後續操作。常見的 Microtasks 包括:

  • Promise.thencatch 回調
  • queueMicrotask API
  • MutationObserver 回調

而宏任務(Macrotasks)則是執行優先級較低的任務,例如:

  • setTimeoutsetInterval 回調
  • setImmediate(Node.js 特有)
  • I/O 任務
  • UI 渲染任務

2. 事件循環的工作方式

事件循環的核心原則是在執行每一個宏任務之前,會清空所有的微任務隊列。執行順序如下:

  1. 執行當前的執行上下文(例如,主程式)。
  2. 清空所有 Microtasks。
  3. 從 Task Queue 中取出一個 Macrotask 並執行。
  4. 回到第 2 步,重複此過程。

3. 為什麼 Microtasks 優先?

a. 保持操作的連續性

微任務的目的是讓代碼能夠快速處理細微的後續工作,而不會中斷當前的執行上下文。例如,Promise 的回調允許在異步操作完成後立即執行後續邏輯,而不需要等待下一個宏任務。

b. 減少不必要的延遲

如果 Microtasks 必須等到下一個 Macrotask 執行後才執行,這可能會導致不必要的延遲。例如:

setTimeout(() => console.log('Macrotask'), 0);
Promise.resolve().then(() => console.log('Microtask'));

按照 Microtasks 先執行的原則,Promise 回調中的微任務會比 setTimeout 的宏任務更早執行。輸出結果為:

Microtask
Macrotask

如果改成先執行宏任務,微任務將不得不等到下一個事件循環才執行,影響代碼的響應性。

c. 符合開發者的直覺

Microtasks 被設計成與當前執行上下文緊密相關。這樣的設計可以避免在處理異步操作時,開發者不得不手動管理細小的任務優先級。

d. 性能和穩定性考量

Microtasks 允許在執行下一個宏任務之前,快速處理一批高優先級任務,從而減少宏任務之間的干擾,提升應用的整體性能和穩定性。

4. 結論

Microtasks 優先於 Macrotasks 的執行順序是為了在語言層面上平衡異步操作的性能和易用性,確保快速響應和一致的執行順序,從而提供更好的開發者體驗。


上一篇
Day3-JavaScript 記憶體管理
下一篇
Day5-JavaScript Execution Context & Hoisting & Scope Chain
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

1
json_liang
iT邦研究生 4 級 ‧ 2022-09-04 10:36:22

感謝大大解說,讓我能夠理解非同步概念

harry xie iT邦研究生 1 級 ‧ 2022-09-04 10:50:35 檢舉

謝謝支持!

2
雷N
iT邦研究生 1 級 ‧ 2022-09-04 13:06:40

這些概念與NodeJS的處理非同步的機制很像耶!
講的淺顯易懂 讚

我要留言

立即登入留言